/**************************************************************************
 * Copyright (C) 2017-2017  Unisound
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 **************************************************************************
 *
 * Description : uni_mp3_player.c
 * Author      : shangjinlong@unisound.com
 * Date        : 2020.02.10
 *
 **************************************************************************/

#include "uni_mp3_player.h"
#include "uni_local_source.h"
#include "uni_log.h"
#include "uni_iot.h"
#include "mp3dec.h"
#include "uni_audio_player.h"
#include "uni_databuf.h"

#define TAG "mp3"

typedef struct {
  uni_bool      decode_task_running;
  uni_sem_t     start_decode_sem;
  uni_sem_t     decoding_sem;
  uni_bool      decode_finsh;
  uni_bool      force_stopped;
  char          inbuf[1024];
  char          outbuf[1152 * 2];
  char          file_name[32];
  DataBufHandle decode_databuf;
} Mp3Player;

static Mp3Player g_mp3player;

static void _set_decode_status(uni_bool decode_finshed) {
  g_mp3player.decode_finsh = decode_finshed;
}

static int _decode(HMP3Decoder hMP3Decoder,
                   unsigned char *buf, int inlen,
                   short *output) {
  int remain = inlen;
  unsigned char *decode_buf = buf;
  while (!g_mp3player.force_stopped) {
    if (inlen < 24) {
      return inlen;
    }

    int offset = MP3FindSyncWord(decode_buf, remain);
    if (offset < 0) {
      return 0;
    }

    int bytes_left = remain - offset;
    decode_buf += offset;
    int errs = MP3Decode(hMP3Decoder, &decode_buf, &bytes_left, output, 0);
    if (ERR_MP3_NONE == errs) {
      MP3FrameInfo mp3FrameInfo;
      MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);

      while (DataBufferGetFreeSize(g_mp3player.decode_databuf) <
             mp3FrameInfo.outputSamps * 2) {
        if (g_mp3player.force_stopped) {
          return -1;
        }

        uni_msleep(5);
      }

      DataBufferWrite(g_mp3player.decode_databuf,
                      (char *)output, mp3FrameInfo.outputSamps * 2);
      remain = bytes_left;
      continue;
    }

    if (errs == ERR_MP3_MAINDATA_UNDERFLOW) {
      return bytes_left;
    }

    if (errs == ERR_MP3_INDATA_UNDERFLOW) {
      return remain;
    }

    if (errs == ERR_MP3_INVALID_SCALEFACT) {
      return -1;
    }

    return 0;
  }

  return -1;
}

static void _stop_local_source() {
  LocalSourceLoad(NULL, NULL, 0, SOURCE_STOP);
}

static void _decode_one_stream() {
  int last_decode_left_bytes = 0;
  int file_read_len;
  int decode_in_len;

  _set_decode_status(false);
  uni_sem_post(g_mp3player.decoding_sem);

  HMP3Decoder hMP3Decoder = MP3InitDecoder();

  while (!g_mp3player.force_stopped) {
    file_read_len = sizeof(g_mp3player.inbuf) - last_decode_left_bytes;
    file_read_len = LocalSourceLoad(g_mp3player.file_name, g_mp3player.inbuf + \
                                    last_decode_left_bytes,
                                    file_read_len, SOURCE_RETRIEVE);
    if (AUDIO_RETRIEVE_DATA_FINISHED == file_read_len) {
      LOGT(TAG, "decode %s finish", g_mp3player.file_name);
      break;
    }

    decode_in_len = file_read_len + last_decode_left_bytes;
    last_decode_left_bytes = _decode(hMP3Decoder,
                                     (unsigned char *)g_mp3player.inbuf,
                                     decode_in_len,
                                     (short *)g_mp3player.outbuf);
    if (-1 == last_decode_left_bytes) {
      break;
    }

    if (last_decode_left_bytes > 0 && decode_in_len != last_decode_left_bytes) {
      memmove(g_mp3player.inbuf,
              &g_mp3player.inbuf[decode_in_len - last_decode_left_bytes],
              last_decode_left_bytes);
    }
  }

  MP3FreeDecoder(hMP3Decoder);
  _stop_local_source();
  _set_decode_status(true);
}

static void _mp3_decode_task(void *args) {
  _set_decode_status(true);
  while (g_mp3player.decode_task_running) {
    uni_sem_wait(g_mp3player.start_decode_sem);
    LOGT(TAG, "start new decode, filename=%s", g_mp3player.file_name);
    _decode_one_stream();
    LOGT(TAG, "decode done, filename=%s", g_mp3player.file_name);
  }
}

static void _create_mp3_decode_thread() {
  uni_pthread_t pid;
  struct thread_param param;
  uni_memset(&param, 0, sizeof(param));
  param.stack_size = STACK_MIDDLE_SIZE;
  param.th_priority = OS_PRIORITY_HIGH;
  uni_strncpy(param.task_name, "mp3", sizeof(param.task_name) - 1);
  g_mp3player.decode_task_running = true;
  uni_pthread_create(&pid, &param, _mp3_decode_task, NULL);
}

static void _sem_create() {
  uni_sem_init(&g_mp3player.start_decode_sem, 0);
  uni_sem_init(&g_mp3player.decoding_sem, 0);
}

static void _sem_destroy() {
  uni_sem_destroy(g_mp3player.start_decode_sem);
  uni_sem_destroy(g_mp3player.decoding_sem);
}

static void _databuf_create() {
  g_mp3player.decode_databuf = DataBufferCreate(sizeof(g_mp3player.outbuf) * 4);
}

static void _databuf_destroy() {
  DataBufferDestroy(g_mp3player.decode_databuf);
}

static void _set_force_stop_flag(uni_bool force_stop) {
  g_mp3player.force_stopped = force_stop;
}

Result Mp3Init(AudioParam *param) {
  memset(&g_mp3player, 0, sizeof(Mp3Player));
  _sem_create();
  _databuf_create();
  _create_mp3_decode_thread();
  LOGT(TAG, "mp3 init success");
  return E_OK;
}

Result Mp3Final(void) {
  g_mp3player.decode_task_running = false;
  _set_force_stop_flag(true);
  uni_sem_post(g_mp3player.start_decode_sem);
  _sem_destroy();
  _databuf_destroy();
  return E_OK;
}

uni_bool Mp3CheckIsPlaying(void) {
  return false;
}

static void _set_decode_filename(const char *filename) {
  strncpy(g_mp3player.file_name, filename, sizeof(g_mp3player.file_name));
}

static void _sync_decoding() {
  uni_sem_wait(g_mp3player.decoding_sem);
}

static void _decode_start_trigger() {
  uni_sem_post(g_mp3player.start_decode_sem);
}

static uni_bool _decode_finish() {
  return g_mp3player.decode_finsh;
}

static uni_bool _retrieve_finsh() {
  return (_decode_finish() &&
          0 == DataBufferGetDataSize(g_mp3player.decode_databuf));
}

static int _pcm_retrieve(DataBufHandle audio_databuf) {
  int free_size, read_size;
  static char buf[1024];

  if (_retrieve_finsh()) {
    LOGT(TAG, "retrieve mp3 finsh");
    return AUDIO_RETRIEVE_DATA_FINISHED;
  }

  free_size = DataBufferGetFreeSize(audio_databuf);
  read_size = DataBufferGetDataSize(g_mp3player.decode_databuf);
  read_size = uni_min(free_size, read_size);
  read_size = uni_min(sizeof(buf), read_size);
  if (read_size <= 0) {
    LOGT(TAG, "no more data");
    return 0;
  }

  DataBufferRead(buf, read_size, g_mp3player.decode_databuf);
  DataBufferWrite(audio_databuf, buf, read_size);
  return read_size;
}

Result Mp3Play(const char *filename) {
  LOGT(TAG, "start play [%s]", filename);
  if (0 == LocalSourceIsExist(filename)) {
    return E_FAILED;
  }
  _set_decode_filename(filename);
  DataBufferClear(g_mp3player.decode_databuf);
  _set_force_stop_flag(false);
  _decode_start_trigger();
  _sync_decoding();
  AudioPlayerStart(_pcm_retrieve);
  LOGT(TAG, "start mp3 play success");
  return E_OK;
}

Result Mp3Stop(void) {
  AudioPlayerStop();
  _set_force_stop_flag(true);
  while (!_decode_finish()) {
    uni_msleep(5);
  }
  DataBufferClear(g_mp3player.decode_databuf);
  LOGT(TAG, "mp3 play stopped");
  return E_OK;
}

